// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package xcoff

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
)

const (
	SAIAMAG   = 0x8
	AIAFMAG   = "`\n"
	AIAMAG    = "<aiaff>\n"
	AIAMAGBIG = "<bigaf>\n"

	// 大小
	FL_HSZ_BIG = 0x80
	AR_HSZ_BIG = 0x70
)

type bigarFileHeader struct {
	Flmagic    [SAIAMAG]byte // 存档魔术字符串
	Flmemoff   [20]byte      // 成员表偏移量
	Flgstoff   [20]byte      // 32位全局符号表偏移量
	Flgst64off [20]byte      // 64位全局符号表偏移量
	Flfstmoff  [20]byte      // 第一个成员偏移量
	Fllstmoff  [20]byte      // 最后一个成员偏移量
	Flfreeoff  [20]byte      // 自由列表上的第一个成员偏移量
}

type bigarMemberHeader struct {
	Arsize   [20]byte // 文件成员大小
	Arnxtmem [20]byte // 下一个成员指针
	Arprvmem [20]byte // 上一个成员指针
	Ardate   [12]byte // 文件成员日期
	Aruid    [12]byte // 文件成员uid 
	Argid    [12]byte // 文件成员gid 
	Armode   [12]byte // 文件成员模式（八进制）
	Arnamlen [4]byte  // 文件成员名称长度
	// /\u aru\nam被删除，因为没有它更容易获得名称。
}

// 归档代表一个开放的AIX大归档。
type Archive struct {
	ArchiveHeader
	Members []*Member

	closer io.Closer
}

// MemberHeader保存有关大归档文件头的信息
type ArchiveHeader struct {
	magic string
}

// 成员表示AIX大归档文件的成员。
type Member struct {
	MemberHeader
	sr *io.SectionReader
}

// MemberHeader保存有关大存档成员的信息
type MemberHeader struct {
	Name string
	Size uint64
}

// OpenArchive使用操作系统打开命名的存档。打开并准备将其用作AIX大存档。
func OpenArchive(name string) (*Archive, error) {
	f, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	arch, err := NewArchive(f)
	if err != nil {
		f.Close()
		return nil, err
	}
	arch.closer = f
	return arch, nil
}

// 关闭存档。
// 如果直接使用NewArchive而不是OpenArchive创建存档，则
// 关闭无效。
func (a *Archive) Close() error {
	var err error
	if a.closer != nil {
		err = a.closer.Close()
		a.closer = nil
	}
	return err
}

// NewArchive创建一个新的归档文件，用于访问底层读取器中的AIX大归档文件。
func NewArchive(r io.ReaderAt) (*Archive, error) {
	parseDecimalBytes := func(b []byte) (int64, error) {
		return strconv.ParseInt(strings.TrimSpace(string(b)), 10, 64)
	}
	sr := io.NewSectionReader(r, 0, 1<<63-1)

	// 读取文件头
	var magic [SAIAMAG]byte
	if _, err := sr.ReadAt(magic[:], 0); err != nil {
		return nil, err
	}

	arch := new(Archive)
	switch string(magic[:]) {
	case AIAMAGBIG:
		arch.magic = string(magic[:])
	case AIAMAG:
		return nil, fmt.Errorf("small AIX archive not supported")
	default:
		return nil, fmt.Errorf("unrecognised archive magic: 0x%x", magic)
	}

	var fhdr bigarFileHeader
	if _, err := sr.Seek(0, os.SEEK_SET); err != nil {
		return nil, err
	}
	if err := binary.Read(sr, binary.BigEndian, &fhdr); err != nil {
		return nil, err
	}

	off, err := parseDecimalBytes(fhdr.Flfstmoff[:])
	if err != nil {
		return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", fhdr, err)
	}

	if off == 0 {
		// 如果存档为空，则会发生。
		return arch, nil
	}

	lastoff, err := parseDecimalBytes(fhdr.Fllstmoff[:])
	if err != nil {
		return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", fhdr, err)
	}

	// 读取成员
	for {
		// 读取成员头
		// 成员头通常大2字节。但是，如果读取标题时没有_ar_nam，则读取名称更容易。
		// 但是，AIAFMAG必须在之后阅读。
		if _, err := sr.Seek(off, os.SEEK_SET); err != nil {
			return nil, err
		}

		var mhdr bigarMemberHeader
		if err := binary.Read(sr, binary.BigEndian, &mhdr); err != nil {
			return nil, err
		}

		member := new(Member)
		arch.Members = append(arch.Members, member)

		size, err := parseDecimalBytes(mhdr.Arsize[:])
		if err != nil {
			return nil, fmt.Errorf("error parsing size in member header(%q); %v", mhdr, err)
		}
		member.Size = uint64(size)

		// 读取名称
		namlen, err := parseDecimalBytes(mhdr.Arnamlen[:])
		if err != nil {
			return nil, fmt.Errorf("error parsing name length in member header(%q); %v", mhdr, err)
		}
		name := make([]byte, namlen)
		if err := binary.Read(sr, binary.BigEndian, name); err != nil {
			return nil, err
		}
		member.Name = string(name)

		fileoff := off + AR_HSZ_BIG + namlen
		if fileoff&1 != 0 {
			fileoff++
			if _, err := sr.Seek(1, os.SEEK_CUR); err != nil {
				return nil, err
			}
		}

		// 读取AIAFMAG字符串
		var fmag [2]byte
		if err := binary.Read(sr, binary.BigEndian, &fmag); err != nil {
			return nil, err
		}
		if string(fmag[:]) != AIAFMAG {
			return nil, fmt.Errorf("AIAFMAG not found after member header")
		}

		fileoff += 2 // 添加AIAFMAG 
		member.sr = io.NewSectionReader(sr, fileoff, size)

		if off == lastoff {
			break
		}
		off, err = parseDecimalBytes(mhdr.Arnxtmem[:])
		if err != nil {
			return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", fhdr, err)
		}

	}

	return arch, nil

}

// GetFile返回由成员名称定义的XCOFF文件。
// 修复程序：如果存档文件中有两个成员具有相同的
// 名称，则此操作无效。如果存档文件同时具有32位和64位文件，则可能会出现这种情况。
func (arch *Archive) GetFile(name string) (*File, error) {
	for _, mem := range arch.Members {
		if mem.Name == name {
			return NewFile(mem.sr)
		}
	}
	return nil, fmt.Errorf("unknown member %s in archive", name)

}
